from loguru import logger
import time
import asyncio
import json
from decimal import Decimal, ROUND_DOWN
from typing import Dict, List, Tuple, Optional
from logs import setup_arbitrage_logger
from exchange_manager import ExchangeManager
from database_manager import DatabaseManager
import os
from datetime import datetime


# 从.env文件读取配置，使用默认值
PRICE_DIFFERENCE_THRESHOLD = 1.2  # 价差阈值
REDIS_KEY = "arbitrage:opportunities:current"  # Redis固定键名


class ArbitrageMonitor:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(ArbitrageMonitor, cls).__new__(cls)
            cls._instance.__init__()
        return cls._instance

    def __init__(self):
        if not hasattr(self, 'initialized'):
            self.logger = setup_arbitrage_logger()
            self.global_prices: Dict[str, Dict[str, Dict]] = {}
            self.global_diff_percents: Dict[str, Dict] = {}
            self.price_lock = asyncio.Lock()
            self.diff_percents_lock = asyncio.Lock()
            self.timestamp_exchange = None
            self.db_manager = DatabaseManager()  # 初始化数据库管理器
            self.initialized = True

    async def _monitor_exchange(self, exchange_id: str, exchange, symbols: List[str]):
        """监控单个交易所的价格并更新全局数据"""

        func_name = 'watchTickers'
        if not exchange.has[func_name]:
            print(f"{exchange_id} 不支持 {func_name}")
            return

        # 保存第一个交易所实例用于时间戳转换
        if self.timestamp_exchange is None:
            self.timestamp_exchange = exchange

        while True:
            try:
                tickers = await getattr(exchange, func_name)(symbols, params={})
                async with self.price_lock:
                    for symbol, ticker in tickers.items():
                        if symbol not in self.global_prices:
                            self.global_prices[symbol] = {}
                        
                        # 只使用last价格，因为bid和ask可能为None
                        last = ticker.get('last')
                        
                        # 只有当last不为None时才更新价格
                        if last is not None:
                            # 保存最新成交价和时间戳
                            self.global_prices[symbol][exchange_id] = {
                                'last': last,
                                'timestamp': exchange.milliseconds()
                            }

                # 移除触发价差检查，由单独任务处理
            except Exception as e:
                print(f"交易所 {exchange_id} 错误: {e}")
                if hasattr(exchange, 'unwatchTickers'):
                    await exchange.unwatchTickers()
                break

    async def price_diff_monitor(self):
        """独立的价差监控任务，定期检查所有交易所的价格数据"""
        while True:
            # 定期检查价差，避免频繁计算
            await asyncio.sleep(1)  # 每秒检查一次
            
            # 确保有timestamp_exchange可用
            if self.timestamp_exchange is None:
                continue
                
            await self.check_spread(self.timestamp_exchange)


    async def check_spread(self, exchange):
        """检查所有交易对的价差并找出大于阈值的交易机会"""
        
        async with self.price_lock:
            current_time = exchange.milliseconds()
            
            for symbol, exchanges in self.global_prices.items():
                # 收集各交易所的价格数据
                valid_data = []
                for ex_id, data in exchanges.items():
                    # 确保数据存在且不为None，并且是最近5秒内的数据
                    if current_time - data['timestamp'] < 5000 and data['last'] is not None:
                        valid_data.append({
                            'exchange_id': ex_id,
                            'last': data['last'],
                            'timestamp': data['timestamp']
                        })
                
                if len(valid_data) < 2:
                    continue  # 至少需要两个交易所的数据
                
                try:
                    # 找到最高价和最低价的交易所
                    max_price_entry = max(valid_data, key=lambda x: x['last'])
                    min_price_entry = min(valid_data, key=lambda x: x['last'])
                    
                    max_price = max_price_entry['last']
                    min_price = min_price_entry['last']
                    max_exchange = max_price_entry['exchange_id']
                    min_exchange = min_price_entry['exchange_id']
                    
                    # 计算价差百分比
                    price_diff = ((max_price - min_price) / min_price) * 100

                    # 如果价差大于阈值
                    if price_diff >= PRICE_DIFFERENCE_THRESHOLD:
                        # 创建套利机会对象 - 高价交易所卖出，低价交易所买入
                        opportunity = {
                            'symbol': symbol,
                            'sell_exchange': max_exchange,  # 高价交易所卖出
                            'buy_exchange': min_exchange,   # 低价交易所买入
                            'sell_price': max_price,  # 保持原始价格精度
                            'buy_price': min_price,   # 保持原始价格精度
                            'price_diff': price_diff,  # 保存原始价差值，不格式化
                            'is_trading': False,
                            'timestamp': current_time
                        }

                        # 更新全局价差（使用锁保护）
                        async with self.diff_percents_lock:
                            self.global_diff_percents[symbol] = opportunity

                        # 立即排序
                        await self.sort_global_diff_percents()
                        
                except Exception as e:
                    print(f"检查价差时出错 ({symbol}): {e}")



    async def sort_global_diff_percents(self):
        """处理套利机会数据"""
        async with self.diff_percents_lock:
            # 1. 清理超过30秒的数据
            current_time = self.timestamp_exchange.milliseconds() if self.timestamp_exchange else 0
            self.global_diff_percents = {symbol: opp for symbol, opp in self.global_diff_percents.items() 
                                      if current_time - opp['timestamp'] < 30000}
            
            # 2. 按价差排序（从高到低）
            sorted_items = sorted(
                self.global_diff_percents.items(),
                key=lambda x: float(x[1]['price_diff']),  # 确保转换为float类型进行比较
                reverse=True
            )
            
            # 3. 限制最多保存50条数据
            if len(sorted_items) > 50:
                sorted_items = sorted_items[:50]
            
            # 4. 更新全局数据
            self.global_diff_percents = dict(sorted_items)
            
            # 5. 保存到Redis
            await self.save_opportunities_to_redis()
            

    async def save_opportunities_to_redis(self):
        """将套利机会保存到Redis"""
        try:
            redis_client = self.db_manager.get_redis_client()
            
            # 将套利机会数据转换为JSON字符串
            opportunities_data = json.dumps(self.global_diff_percents)
            
            # 使用固定key保存到Redis，设置过期时间为1小时
            redis_client.setex(REDIS_KEY, 3600, opportunities_data)
            
            self.logger.debug(f"成功保存套利机会数据到Redis: {REDIS_KEY}")
            
        except Exception as e:
            self.logger.error(f"保存套利机会数据到Redis失败: {str(e)}")



    async def print_diff_ranking_list(self):
        """打印排序后的套利机会排行榜"""
        while True:
            if not self.global_diff_percents:
                await asyncio.sleep(5)  # 没有数据时等待5秒再检查
                continue
            
            # 获取当前时间
            current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            
            # 构建排行榜内容
            leaderboard = []
            leaderboard.append("\n=== 套利机会排行榜 ===")
            leaderboard.append(f"更新时间: {current_time}")
            leaderboard.append(f"总机会数: {len(self.global_diff_percents)}")
            
            # 定义列宽
            rank_width = 4
            symbol_width = 12
            diff_width = 10
            exchange_width = 10
            price_width = 12
            
            # 构建表头格式
            header = (
                f"{'排名':>{rank_width}} | "
                f"{'交易对':^{symbol_width-4}} | "
                f"{'价差':^{diff_width}} | "
                f"{'买入交易所':^{exchange_width}} | "
                f"{'买入价格':^{price_width}} | "
                f"{'卖出交易所':^{exchange_width}} | "
                f"{'卖出价格':^{price_width}}"
            )
            leaderboard.append("\n\n" + header)
            leaderboard.append("-" * (rank_width + symbol_width + diff_width + (exchange_width + price_width) * 2 + 20))
            
            # 构建数据行
            for i, (symbol, opp) in enumerate(self.global_diff_percents.items(), 1):
                price_diff_str = f"{opp['price_diff']:.2f}%"
                spaces = ' ' * (diff_width - len(price_diff_str))
                row = (
                    f"{i:>{rank_width}} | "
                    f"{symbol:<{symbol_width}} | "
                    f"{price_diff_str}{spaces} | "
                    f"{opp['buy_exchange']:^{exchange_width}} | "
                    f"{opp['buy_price']:^{price_width}.8f} | "
                    f"{opp['sell_exchange']:^{exchange_width}} | "
                    f"{opp['sell_price']:^{price_width}.8f}"
                )
                leaderboard.append(row)
            
            leaderboard.append("-" * (rank_width + symbol_width + diff_width + (exchange_width + price_width) * 2 + 20))
            
            # 将排行榜内容拼接为字符串
            leaderboard_str = "\n".join(leaderboard)
            
            # 打印到控制台
            print(leaderboard_str)
            
            # 记录到日志
            self.logger.info(leaderboard_str)
            
            await asyncio.sleep(10)  # 每10秒更新一次
        
        
    async def print_redis_diff_ranking_list(self):
        """打印Redis中的套利机会排行榜"""
        while True:
            try:
                # 从Redis获取数据
                redis_client = self.db_manager.get_redis_client()
                data = redis_client.get(REDIS_KEY)
                
                if not data:
                    self.logger.info("Redis中没有找到套利机会数据")
                    await asyncio.sleep(5)
                    continue
                
                # 解析JSON数据
                opportunities = json.loads(data)
                
                if not opportunities:
                    self.logger.info("当前没有套利机会")
                    await asyncio.sleep(5)
                    continue
                
                # 获取当前时间
                current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                
                # 构建排行榜内容
                leaderboard = []
                leaderboard.append("\n=== 套利机会排行榜 ===")
                leaderboard.append(f"更新时间: {current_time}")
                leaderboard.append(f"总机会数: {len(opportunities)}")
                
                # 定义列宽
                rank_width = 4
                symbol_width = 12
                diff_width = 10
                exchange_width = 10
                price_width = 12
                
                # 构建表头格式
                header = (
                    f"{'排名':>{rank_width}} | "
                    f"{'交易对':^{symbol_width-4}} | "
                    f"{'价差':^{diff_width}} | "
                    f"{'买入交易所':^{exchange_width}} | "
                    f"{'买入价格':^{price_width}} | "
                    f"{'卖出交易所':^{exchange_width}} | "
                    f"{'卖出价格':^{price_width}}"
                )
                leaderboard.append("\n\n" + header)
                leaderboard.append("-" * (rank_width + symbol_width + diff_width + (exchange_width + price_width) * 2 + 20))
                
                # 构建数据行
                for i, (symbol, opp) in enumerate(opportunities.items(), 1):
                    row = (
                        f"{i:>{rank_width}} | "
                        f"{symbol:<{symbol_width}} | "
                        f"{opp['price_diff']:.2f}%{' ' * (diff_width - len(f'{opp['price_diff']:.2f}%'))} | "
                        f"{opp['buy_exchange']:^{exchange_width}} | "
                        f"{opp['buy_price']:^{price_width}.8f} | "
                        f"{opp['sell_exchange']:^{exchange_width}} | "
                        f"{opp['sell_price']:^{price_width}.8f}"
                    )
                    leaderboard.append(row)
                
                leaderboard.append("-" * (rank_width + symbol_width + diff_width + (exchange_width + price_width) * 2 + 20))
                
                # 将排行榜内容拼接为字符串
                leaderboard_str = "\n".join(leaderboard)
                
                # 打印到控制台
                print(leaderboard_str)
                
                # 记录到日志
                self.logger.info(leaderboard_str)
                
            except Exception as e:
                self.logger.error(f"打印排行榜时出错: {str(e)}")
            
            await asyncio.sleep(10)  # 每10秒更新一次


    # 获取排序后的套利机会排行榜
    async def get_sorted_diff_percents(self) -> Dict[str, Dict]:
        """获取排序后的套利机会排行榜"""
        async with self.diff_percents_lock:
            return self.global_diff_percents.copy()
        


    async def start(self, common_symbols: List[str]):
        """运行套利监控"""
         # 初始化交易所管理器
        exchange_manager = ExchangeManager()

        try:
            tasks = []
            # 启动交易所价格监控任务
            for exchange_id, exchange in exchange_manager.get_all_exchanges().items():
                print(f"启动监控: {exchange_id} - {exchange.name} - 支持watchTickers: {exchange.has['watchTickers']}")
                task = asyncio.create_task(self._monitor_exchange(exchange_id, exchange, common_symbols))
                tasks.append(task)
            
            # 添加独立的价差监控任务
            price_diff_task = asyncio.create_task(self.price_diff_monitor())
            tasks.append(price_diff_task)

            # # 添加独立的打印排行榜任务
            print_task = asyncio.create_task(self.print_diff_ranking_list())
            tasks.append(print_task)

            # 添加独立的打印Redis排行榜任务
            # redis_print_task = asyncio.create_task(self.print_redis_diff_ranking_list())
            # tasks.append(redis_print_task)
        
            await asyncio.gather(*tasks)

        finally:
            await exchange_manager.close_all_exchanges()


    # def run(self, common_symbols: List[str]):
    #     """启动套利监控"""
    #     asyncio.run(self.start(common_symbols))